#include <i386/io.h>
#include <i386/console.h>
#include <i386/string.h>
#include <i386/stdlib.h>
#include <i386/stdio.h>
#include <stdarg.h>

// CGA compatible registers
#define CGA_INDEX_REG		0x3D4
#define CGA_DATA_REG		0x3D5

// CGA compatible registers values
#define CURSOR_START		0x0A
#define CURSOR_END			0x0B
#define VIDEO_ADDRESS_MSB	0x0C
#define VIDEO_ADDRESS_LSB	0x0D
#define CURSOR_POS_MSB		0x0E
#define CURSOR_POS_LSB		0x0F

static unsigned char bios_x, bios_y, bios_start, bios_end, bios_attr;

// active video page-buffer
#define PAGE_SIZE			2048
#define PAGE_MAX			8

static int active_page = 0;
static int visual_page = 0;
static int curs_x[PAGE_MAX];
static int curs_y[PAGE_MAX];

static struct {
	int x1, y1, x2, y2;
} view_window = {
	0, 0, 79, 24
};

void set_visual_page(int page) {
	unsigned short page_offset = page*PAGE_SIZE;
	visual_page = page;
	outp(CGA_INDEX_REG, VIDEO_ADDRESS_LSB);
	outp(CGA_DATA_REG, page_offset & 0xFF);
	outp(CGA_INDEX_REG, VIDEO_ADDRESS_MSB);
	outp(CGA_DATA_REG, (page_offset >> 8) & 0xFF);
}

void set_active_page(int page) {
	curs_x[active_page] = bios_x;
	curs_y[active_page] = bios_y;
	bios_x = curs_x[page];
	bios_y = curs_y[page];
	active_page = page;
}

int get_visual_page(void) {
	return(visual_page);
}

int get_active_page(void) {
	return(active_page);
}

void place(int x, int y) {
	unsigned short cursor_word = x + y*80 + active_page*PAGE_SIZE;
	outp(CGA_INDEX_REG, CURSOR_POS_LSB);
	outp(CGA_DATA_REG, cursor_word & 0xFF);
	outp(CGA_INDEX_REG, CURSOR_POS_MSB);
	outp(CGA_DATA_REG, (cursor_word >> 8) & 0xFF);
	
	bios_x = x;
	bios_y = y;
}

void cursor(int start,int end) {
	outp(CGA_INDEX_REG, CURSOR_START);
	outp(CGA_DATA_REG, start);
	outp(CGA_INDEX_REG, CURSOR_END);
	outp(CGA_DATA_REG, end);
}

void bios_save(void) {
	bios_attr = *((unsigned char *)0xB8000 + 159);
	bios_x = *((unsigned char *)0x00450);
	bios_y = *((unsigned char *)0x00451);
	bios_end = *((unsigned char *)0x00460);
	bios_start = *((unsigned char *)0x00461);
	active_page = visual_page = 0;
}

void bios_restore(void) {
	*((unsigned char *)0x00450) = bios_x;
	*((unsigned char *)0x00451) = bios_y;
	
	place(bios_x, bios_y);
	cursor(bios_start, bios_end);
}

void window(int x1, int y1, int x2, int y2) {
	view_window.x1 = x1;
	view_window.y1 = y1;
	view_window.x2 = x2;
	view_window.y2 = y2;
	
	//place(x1, y1);
}

void _clear(char c, char attr, int x1, int y1, int x2, int y2) {
	register int i,j;
	unsigned short w = attr;
	w <<= 8; w |= c;
	
	for (i = x1; i <= x2; i++) {
		for (j = y1; j <= y2; j++) { 
			*((unsigned short *)(0xB8000 + 2*i+160*j + 2*active_page*PAGE_SIZE)) = w;
		}
	}
	
	place(x1, y1);
	bios_y = y1;
	bios_x = x1;
}

void clear() {
    _clear(' ', bios_attr, view_window.x1, view_window.y1, view_window.x2,
    	view_window.y2);
}

void _scroll(char attr, int x1, int y1, int x2, int y2) {
	register int x,y;
	unsigned short xattr = attr << 8,w;
	unsigned char *v = (unsigned char *)(0xB8000 + active_page*(2*PAGE_SIZE));
	
	for (y = y1+1; y <= y2; y++) {
		for (x = x1; x <= x2; x++) {
			w = *((unsigned short *) (v + 2*(y*80+x)));
			*((unsigned short *)(v + 2*((y-1)*80+x))) = w;
	    }
	}
	
	for (x = x1; x <= x2; x++) {
		*((unsigned short *)(v + 2*((y-1)*80+x))) = xattr;
	}
}

void scroll(void) {
    _scroll(bios_attr, view_window.x1, view_window.y1, view_window.x2,
    	view_window.y2);
}

void cputc(char c) {
	static unsigned short scan_x, x, y;
	unsigned char *v = (unsigned char *)(0xB8000 + active_page*(2*PAGE_SIZE));
	x = bios_x;
	y = bios_y;
	
	switch (c) {
		case '\t':
			x += 8;
			
			if (x >= view_window.x2+1) {
				x = view_window.x1;
				
				if (y == view_window.y2) {
					scroll();
				} else {
					y++;
				}
			} else {
				scan_x = 0;
				
				while ((scan_x+8) < x) {
					scan_x += 8;
				}
				
				x = scan_x;
			}
			break;
		case '\n':
			x = view_window.x1;
			
			if (y == view_window.y2) {
				scroll();
			} else {
				y++;
			}
			break;
		case '\b':
			x--;
			*(v + 2*(x + y*80)) = ' ';
			x++;
			break;
		default:
			*(v + 2*(x + y*80)) = c;
			x++;
			
			if (x >= view_window.x2+1) {
				x = view_window.x1;
				
				if (y == view_window.y2) {
					scroll();
				} else {
					y++;
				}
			}
			break;
	}
	
	place(x, y);
}

void cputs(char *s) {
	char c;
	while (*s != '\0') {
		c = *s++;
		cputc(c);
    }
}

void puts_xy(int x, int y, char attr, char *s) {
	unsigned char *v = (unsigned char *)(0xB8000 + (80*y+x)*2 + active_page*(2*PAGE_SIZE));
	while (*s != 0) {
		*v = *s; s++; v++;
		*v = attr; v++;
	}
}

void putc_xy(int x, int y, char attr, char c) {
	unsigned char *v = (unsigned char *)(0xB8000 + (80*y+x)*2 + active_page*(2*PAGE_SIZE));
	*v = c; v++;
	*v = attr;
}

char getc_xy(int x, int y, char *attr, char *c) {
	unsigned char *v = (unsigned char *)(0xB8000 + (80*y+x)*2 + active_page*(2*PAGE_SIZE));
	char r;
	r = *v; v++;
	if (c != NULL) {
		*c = r;
	}
	
	r = *v;
	if (attr != NULL) {
		*attr = r;
	}
	
	return r;
}
